package sunwul.datasource.common.plugin;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import sunwul.datasource.db.DynamicDatasource;
import sunwul.datasource.emun.DatabaseOperation;

import java.util.Properties;

/*****
 * @author sunwul
 * @date 2022/2/21 21:21
 * @description mybatis插件的动态数据源切换
 * * @Intercepts 标明这是一个mybatis插件(固定写法,插件是基于动态代理的)
 * * @Signature  设置要代理的对象, 方法, 参数列表(参数列表可以参考Executor源码中对应方法的参数列表)
 * 在mybatis执行操作的时候进行增强, mybatis会通过Executor来执行数据库操作(增删改/查 对应Executor中的update/query方法)
 * 因此,只需要代理Executor中的update与query方法
 */
// @Component // 只需要成为SpringBean, 在MybatisAutoConfiguration实例化时,会扫描到此Bean,自动注入  (这里不使用注解的方式,新建一个配置类创建Bean,便于管理)
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})
})
public class DynamicDataSourcePlugin implements Interceptor {

    /**
     * 拦截操作
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取当前方法的参数列表
        Object[] objects = invocation.getArgs();
        // 根据Executor类的源码可知, 不管是update还是query方法,参数列表中第一个参数都是MappedStatement
        // MappedStatement中封装了一个完整的SQL所对应的元素(参考我们平时写的**Mapper.xml, 有命令类型,id,返回结果类型...)
        MappedStatement ms = (MappedStatement) objects[0];

        // 根据SQL类型改变数据源标识,这样在执行数据库命令时,动态数据源就会根据数据源标识切换到对应数据源
        if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
            System.out.println("==============检测到[读]操作,切换数据源标识为[读]=====================");
            // 读
            DynamicDatasource.name.set(DatabaseOperation.Read.getName());
        } else {
            System.out.println("==============检测到[写]操作,切换数据源标识为[写]====================");
            // 写
            DynamicDatasource.name.set(DatabaseOperation.Write.getName());
        }
        return invocation.proceed();
    }

    /**
     * 在新建可代理对象时,会通过此方法来决定是返回目标对象本身还是代理对象
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            // 参考Interceptor类源码
            return Plugin.wrap(target, this);
        }
        return target;
    }

    /**
     * 此方法会在Configuration初始化当前的Interceptor时执行
     */
    @Override
    public void setProperties(Properties properties) {

    }

}
